package remote

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/schollz/progressbar/v3"

	"gitee.com/dark.H/go-remote-repl/datas"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

type Ssh string
type Auth struct {
	Type   string
	Target string
	User   string
	Pwd    string
	Extend string
}

// protcol://ip:port:user:pwd
// pro://ip:port@user:pwd
func ParseStringToBrute(s string) (a *Auth, err error) {
	fs := strings.SplitN(s, "@", 2)
	if len(fs) != 2 {
		var name, pwd string
		// name := prompt.Input("user >", func(d prompt.Document) (s []prompt.Suggest) {
		// 	s = append(s, prompt.Suggest{
		// 		Text:        "root",
		// 		Description: "default user",
		// 	})
		// 	if d.GetWordBeforeCursor() != "" {
		// 		return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
		// 	} else {
		// 		return prompt.FilterHasPrefix(s, "root", true)
		// 	}
		// })

		// pwd := prompt.Input("password >", func(d prompt.Document) (s []prompt.Suggest) {
		// 	for _, o := range datas.GetHistory() {
		// 		if !strings.Contains(o, " ") && len(o) < 40 {
		// 			s = append(s, prompt.Suggest{
		// 				Text:        o,
		// 				Description: "from SHELL History",
		// 			})
		// 		}
		// 	}
		// 	return prompt.FilterContains(s, d.GetWordBeforeCursor(), true)
		// })
		if name == "" {
			name = "root"
		}
		// if pwd == "" {
		// 	return nil, errors.New("must |  protcol://ip:port@user:pwd you are:" + s)
		// }

		fs = append(fs, name+":"+pwd)
	}
	afs := strings.SplitN(fs[1], ":", 2)
	if len(afs) != 2 {
		return nil, errors.New("must |  user:pwd, you are:" + s)
	}
	protocl := strings.SplitN(fs[0], "://", 2)[0]
	addr := strings.SplitN(fs[0], "://", 2)[1]
	a = new(Auth)
	a.Type = protocl
	a.Target = addr
	a.User = afs[0]
	a.Pwd = afs[1]
	return
}

func (sshstr Ssh) GetIdRsa() (ssh.Signer, error) {
	home, _ := os.UserHomeDir()
	file := filepath.Join(home, ".ssh", "id_rsa")
	buf, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}
	// authorizedKeysBytes, _ := ioutil.ReadFile(filepath.Join(home, ".ssh", "id_rsa.pub"))
	// pcert, _, _, _, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)

	upkey, err := ssh.ParsePrivateKey(buf)
	if err != nil {
		return nil, err
	}
	return upkey, nil

	// usigner, err := ssh.NewSignerFromKey(upkey)
	// if err != nil {
	// 	log.Printf("Failed to create new signer, err: %v", err)
	// }
	// log.Printf("signer: %v", usigner)

	// ucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)

	// if err != nil {
	// 	log.Printf("Failed to create new signer, err: %v", err)
	// }

	// return ucertSigner, nil
}

func (sshstr Ssh) Connected() (*Auth, *ssh.Client, error) {
	auth, err := ParseStringToBrute(string(sshstr))
	if err != nil {
		log.Println(err)
		return nil, nil, err
	}

	methods := []ssh.AuthMethod{}
	if signer, err := sshstr.GetIdRsa(); err == nil {
		methods = append(methods, ssh.PublicKeys(signer))
	} else {
		fmt.Printf("Now, please type in the password (mandatory): ")
		password, _ := terminal.ReadPassword(0)
		methods = []ssh.AuthMethod{ssh.Password(string(password))}
	}

	if !strings.Contains(auth.Target, ":") {
		auth.Target += ":22"
	}

	sshConfig := &ssh.ClientConfig{
		User: auth.User,
		Auth: methods,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: time.Second * time.Duration(12),
	}
	client, err := ssh.Dial("tcp", auth.Target, sshConfig)
	if err != nil {
		// log.Println(fmt.Errorf("Failed to dial: %s", err))
		fmt.Printf("Now, please type in the password (mandatory): ")
		password, _ := terminal.ReadPassword(0)
		auth.Pwd = string(password)
		methods = []ssh.AuthMethod{ssh.Password(string(password))}
		sshConfig := &ssh.ClientConfig{
			User: auth.User,
			Auth: methods,
			HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
				return nil
			},
			Timeout: time.Second * time.Duration(12),
		}

		client, err = ssh.Dial("tcp", auth.Target, sshConfig)
		if err != nil {
			return nil, nil, err
		}
	}
	return auth, client, nil
}

func (sshstr Ssh) SshShell(cmd string, connectedDo ...func(cli *ssh.Client)) string {
	// rawHost := strings.Split(strings.Split(string(sshstr), "://")[1], "@")[0]
	_, client, err := sshstr.Connected()
	if err != nil {
		return err.Error()
	}
	fmt.Println(datas.Green("---> connect ok"))
	defer func() {
		if connectedDo != nil {
			connectedDo[0](client)
		}
		client.Close()
	}()
	sess, err := client.NewSession()
	if err != nil {
		return err.Error()
	}
	defer sess.Close()

	var b bytes.Buffer
	sess.Stdout = &b
	sess.Stderr = &b
	fmt.Println(datas.Blue("---> running ", cmd))
	if err := sess.Run(cmd); err != nil {
		return err.Error()
	}
	return b.String()
}

func RunByClient(cli *ssh.Client, cmd string) {
	sess, err := cli.NewSession()
	if err != nil {
		return
	}
	defer sess.Close()

	var b bytes.Buffer
	sess.Stdout = &b
	sess.Stderr = &b
	fmt.Println(datas.Blue("---> running "))
	if err := sess.Run(cmd); err != nil {
		log.Println(err)
	}
	fmt.Println(datas.Green(b.String()))
}

func (sshstr Ssh) SshSync(file string, ifremove bool, connectedDo ...func(name string, cli *ssh.Client)) (string, string) {
	auth, client, err := sshstr.Connected()
	if err != nil {
		// log.Println(fmt.Errorf("Failed to dial: %s", err))
		return err.Error(), ""
	}
	fmt.Println(datas.Green("---> connect ok"))
	U := uuid.NewMD5(uuid.UUID{}, []byte(file))
	name := filepath.Join("/tmp", U.String())
	defer func() {
		if connectedDo != nil {
			connectedDo[0](name, client)
		}
		client.Close()
	}()

	srcFile, err := os.Open(file)
	if err != nil {
		return err.Error(), ""
	}
	defer srcFile.Close()
	s, _ := srcFile.Stat()
	len := s.Size()

	ftpCli, err := sftp.NewClient(client)
	if err != nil {
		return err.Error(), ""
	}

	fi, err := ftpCli.Stat(name)

	dst := new(sftp.File)
	bar := new(progressbar.ProgressBar)
	bar = progressbar.DefaultBytes(
		len,
		"Upload by sftp",
	)

	if err == nil {
		if ifremove {
			ftpCli.Remove(name)
			fmt.Println(datas.Blue("remove file then re upload:", name))
			dst, err = ftpCli.Create(name)
			if err != nil {
				return err.Error(), ""
			}
		} else {
			seekOffset := fi.Size()
			if seekOffset < len {
				fmt.Println(datas.Yello("continue with :", seekOffset))
				srcFile.Seek(seekOffset, io.SeekStart)
				dst, err = ftpCli.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND)
				if err != nil {
					return err.Error(), ""
				}
				bar.Add64(seekOffset)
			} else if seekOffset == len {
				fmt.Println(datas.Blue("file uploaded :", name))
				return name, auth.User + ":" + auth.Pwd
			} else {
				ftpCli.Remove(name)
				fmt.Println(datas.Blue("break file re upload:", name))
				dst, err = ftpCli.Create(name)
				if err != nil {
					return err.Error(), ""
				}
			}
		}

	} else {
		fmt.Println(datas.Blue("upload new:", name))
		dst, err = ftpCli.Create(name)
		if err != nil {
			return err.Error(), ""
		}
	}
	if err != nil {
		return err.Error(), ""
	}
	defer dst.Close()

	var out io.Writer
	out = dst

	out = io.MultiWriter(out, bar)
	// datas.Copy(api.con, f)
	io.Copy(out, srcFile)
	ftpCli.Chmod(name, 0x644)
	return name, auth.User + ":" + auth.Pwd
}
